﻿using System;
using System.Collections;
using Microsoft.SPOT.Hardware;
using MSH = Microsoft.SPOT.Hardware;

namespace LBNHelpers
{
	public class I2CManager
	{
		#region Public properties

		static public I2CBus HardwareBus
		{
			get
			{
				if (_HardwareBus == null)
				{
					_HardwareBus = new I2CBus();
				}
				return _HardwareBus;
			}
		}

		static public IEnumerable SoftwareBusList
		{
			get { return _SoftwareBusList; }
		}

		#endregion

		#region Public interface

		static public I2CBus CreateSoftwareBus(Cpu.Pin scl, Cpu.Pin sda, int clock)
		{
			var bus = new I2CBus(scl, sda, clock);
			_SoftwareBusList.Add(bus);

			return bus;
		}

		static public void DeleteSofwareBus(I2CBus bus)
		{
			if (_SoftwareBusList.Contains(bus))
			{
				_SoftwareBusList.Remove(bus);
				bus.Dispose();
			}
		}

		#endregion

		#region Internal data members

		internal static I2CBus _HardwareBus = null;

		internal static ArrayList _SoftwareBusList = new ArrayList();

		#endregion

		#region I2C internal wrapper

		private static MSH.I2CDevice Device = null;

		private static byte[] _PortBuffer = new byte[1];
		private static MSH.I2CDevice.I2CTransaction[] _PortTransaction = null;
		private static MSH.I2CDevice.I2CTransaction[] PortTransaction
		{
			get
			{
				if (_PortTransaction == null)
				{
					_PortTransaction = new MSH.I2CDevice.I2CTransaction[2];
					_PortTransaction[0] = MSH.I2CDevice.CreateWriteTransaction(_PortBuffer);
				}
				return _PortTransaction;
			}
		}

		public static int Execute(ushort address, MSH.I2CDevice.I2CTransaction[] trans, int timeout = 100)
		{
			var dev = GetDevice(address);
			return dev.Execute(trans, timeout);
		}

		public static void ReadPort(ushort address, byte port, ref byte[] result, int timeout = 100)
		{
			_PortBuffer[0] = port;
			PortTransaction[1] = MSH.I2CDevice.CreateReadTransaction(result);

			var dev = GetDevice(address);
			var size = dev.Execute(PortTransaction, timeout);

			if (size != (result.Length + 1))
			{
				throw new I2CExecuteException(size);
			}
		}

		public static byte[] ReadPort(ushort address, byte port, byte sizetoread, int timeout = 100)
		{
			_PortBuffer[0] = port;

			var result = new byte[sizetoread];

			PortTransaction[1] = MSH.I2CDevice.CreateReadTransaction(result);

			var dev = GetDevice(address);
			var size = dev.Execute(PortTransaction, timeout);
			if (size != (sizetoread + 1))
			{
				throw new I2CExecuteException(size);
			}

			return result;
		}

		public static byte ReadPortByte(ushort address, byte port, int timeout = 100)
		{
			var dataarray = new byte[1];
			ReadPort(address, port, ref dataarray, timeout);
			return dataarray[0];
		}

		public static int WritePort(ushort address, byte port, byte[] data, int timeout = 100)
		{
			var td = new byte[data.Length + 1];
			td[0] = port;
			data.CopyTo(td, 1);
			var trans = MSH.I2CDevice.CreateWriteTransaction(td);

			var dev = GetDevice(address);
			return dev.Execute(new MSH.I2CDevice.I2CTransaction[] { trans }, timeout);
		}

		public static int WritePort(ushort address, byte port, byte data, int timeout = 100)
		{
			return WritePort(address, port, new byte[] { data }, timeout);
		}

		public static byte ReadPortByte(SoftwareI2C dev, ushort address, byte port, int timeout = 100)
		{
			var dataarray = new byte[1];
			ReadPort(dev, address, port, ref dataarray, timeout);
			return dataarray[0];
		}

		public static void ReadPort(SoftwareI2C dev, ushort address, byte port, ref byte[] result, int timeout = 100)
		{
			if (dev == null) return;

			_PortBuffer[0] = port;
			PortTransaction[1] = MSH.I2CDevice.CreateReadTransaction(result);

			var size = dev.Execute(address, PortTransaction, timeout);

			if (size != (result.Length + 1))
			{
				throw new I2CExecuteException(size);
			}
		}

		public static byte[] ReadPort(SoftwareI2C dev, ushort address, byte port, byte sizetoread, int timeout = 100)
		{
			_PortBuffer[0] = port;

			var result = new byte[sizetoread];

			PortTransaction[1] = MSH.I2CDevice.CreateReadTransaction(result);

			var size = dev.Execute(address, PortTransaction, timeout);
			if (size != (sizetoread + 1))
			{
				throw new I2CExecuteException(size);
			}

			return result;
		}

		public static int WritePort(SoftwareI2C dev, ushort address, byte port, byte data, int timeout = 100)
		{
			return WritePort(dev, address, port, new byte[] { data }, timeout);
		}

		public static int WritePort(SoftwareI2C dev, ushort address, byte port, byte[] data, int timeout = 100)
		{
			var td = new byte[data.Length + 1];
			td[0] = port;
			data.CopyTo(td, 1);
			var trans = MSH.I2CDevice.CreateWriteTransaction(td);

			return dev.Execute(address, new MSH.I2CDevice.I2CTransaction[] { trans }, timeout);
		}

		private static MSH.I2CDevice GetDevice(ushort address)
		{
			if (Device != null && Device.Config.Address == address) return Device;
			if (Device != null) Device.Dispose();
			Device = new MSH.I2CDevice(new MSH.I2CDevice.Configuration(address, 100));
			return Device;
		}

		#endregion
	}

	public class I2CExecuteException : Exception
	{
		public int SizeTranferred { get; private set; }

		public I2CExecuteException(int sizetransferred, string msg = null)
			: base(msg)
		{
			SizeTranferred = sizetransferred;
		}
	}

	public class BusDestroyedException : Exception { }
}
